package io.gitee.hejiang.jersey;

import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.databind.JavaType;

import io.swagger.inflector.utils.ReflectionUtils;
import io.swagger.models.Model;
import io.swagger.models.properties.Property;
import io.swagger.models.properties.RefProperty;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.LoaderClassPath;
import javassist.Modifier;
import javassist.NotFoundException;

public class JerseyReflectionUtils extends ReflectionUtils {
	private static final Logger LOGGER = LoggerFactory
			.getLogger(JerseyReflectionUtils.class);

	Map<String, Class<?>> models = new HashMap<>();
	ClassLoader loader;
	ClassPool pool;

	@Override
	public Class<?> loadClass(String className) {
		// 因为 Model 在 pool 中定义, 所以需要使用 pool 的类加载器
		try {
			if (loader != null) {
				return loader.loadClass(className);
			} else {
				return super.loadClass(className);
			}
		} catch (ClassNotFoundException e) {
			LOGGER.error(e.getMessage(), e);
			return null;
		}
	}

	public void setClassLoader(ClassLoader cl) {
		this.loader = cl;
	}

	protected void constructModels(Map<String, Model> definitions) {
		// 创建一个 ClassLoader 来放置动态创建的类
		// 这样重新加载时，重新 new DefiningClassLoader() 时
		// 就自然丢掉上次创建的类了
		// loader = new DefiningClassLoader();

		// 参考 ClassPool.getDefault()
		pool = new ClassPool();
		pool.appendSystemPath();
		// 对于 SpringBoot 打的胖包
		// 第三方包和用户程序都是用 SpringBoot 的 ClassLoader 加载的
		pool.appendClassPath(
				new LoaderClassPath(this.getClass().getClassLoader()));

		// 逐一构建 Model 类
		if (definitions != null) {
			definitions.forEach((name, model) -> {
				if (!models.containsKey(name)) {
					constructModel(name, definitions);
				}
			});
		}
	}

	protected void constructModel(String modelName,
			Map<String, Model> definitions) {
		String packageName = config.getModelPackage();
		CtClass ctClass = pool.makeClass(packageName + "." + modelName);

		Model model = definitions.get(modelName);
		Map<String, Property> properties = model.getProperties();
		try {
			for (Map.Entry<String, Property> property : properties.entrySet()) {
				CtField field = constructField(property.getKey(),
						property.getValue(), ctClass, definitions);
				ctClass.addField(field);
			}

			Class<?> clazz = ctClass.toClass(loader, null);
			models.put(modelName, clazz);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private CtField constructField(String name, Property property,
			CtClass ctClass, Map<String, Model> definitions)
			throws NotFoundException, CannotCompileException {
		if (property instanceof RefProperty) {
			RefProperty ref = (RefProperty) property;
			String modelName = ref.getSimpleRef();
			if (definitions.containsKey(modelName)) {
				if (!models.containsKey(modelName)) {
					constructModel(modelName, definitions);
				}
			}
		}

		JavaType type = getTypeFromProperty(property.getType(),
				property.getFormat(), property, definitions);
		Class<?> rawClass = type.getRawClass();
		String fieldClassName = rawClass.getName();
		CtClass fieldClass = pool.get(fieldClassName);
		CtField ctField = new CtField(fieldClass, name, ctClass);
		ctField.setModifiers(Modifier.PUBLIC);

		return ctField;
	}
}
